#include "usb.h"
#include <util/delay.h>
#define EP0_SIZE 64
/****************
* Deskriptoren *
****************/
#define W(x) (x)&0xFF,(x)>>8
#define D(x) W(x&0xFFFF),W(x>>16)
static const byte PROGMEM HidReportDesc[] = {
// Erstes HID-Gerät: USB-Serial-Konverter ohne Strings
0x06, W(0xFFA0), // G Usage Page (0xFFA0 = USB-seriell wie HE2325U)
0x09, 0x03, // L Usage
0xA1, 0x01, // M Collection (Application)
// SerialCfgReport: 4 Byte Feature
0x85, 4, // G Report ID (4)
0x25, 1, // G Logical Maximum (1)
0x09, 0x21, // L Usage (Baudrate)
0x15, 0, // G Logical Minimum (0)
0x27, D(2000000), // G Logical Maximum (2000000)
0x75, 32, // G Report Size (32 Bit)
0x95, 1, // G Report Count (1 Word)
0xB1, 0x02, // M Feature (Var,NoPreferred)
0x09, 0x22, // L Usage (8255-kompatible Register)
0x26, W(255), // G Logical Maximum (255)
0x75, 8, // G Report Size (8 bit)
0x95, 5, // G Report Count (5 Byte)
0xB1, 0xA2, // M Feature (Var,NoPreferred,Volatile)
// SerialInReport: 64 Byte In
0x05, 1, // G Usage Page: Generic Desktop
0x85, 5, // G Report ID (5)
0x09, 0x3B, // L Usage (Byte Count)
0x25, 62, // G Logical Maximum (62)
0x75, 8, // G Report Size (8 Bit)
0x95, 1, // G Report Count (1 Byte)
0x81, 0x02, // M Input (Var,NoPreferred)
0x09, 0x3A, // L Usage (Counted Buffer)
0x26, W(255), // G Logical Maximum (255)
0x95, 62, // G Report Count (62 Byte)
0x82, W(0x102), // M Input (Var,NoPreferred,BufBytes)
// SerialOutReport: 64 Byte Out
0x85, 6, // G Report ID (6)
0x09, 0x3B, // L Usage (Byte Count)
0x25, 62, // G Logical Maximum (62)
0x95, 1, // G Report Count (1 Byte)
0x91, 0x02, // M Output (Var,NoPreferred)
0x09, 0x3A, // L Usage (Counted Buffer)
0x26, W(255), // G Logical Maximum (255)
0x95, 62, // G Report Count (62 Byte)
0x92, W(0x102), // M Output (Var,NoPreferred,BufBytes)
0xC0, // M End Collection
// Zweites HID-Gerät: PnP-Sensor/Aktor mit Strings
0x06, W(0xFF00), // G Usage Page (0xFF00)
0x09, 0x03, // L Usage
0xA1, 0x01, // M Collection (Application)
// DioReport: 2 Byte Feature
0x85, 3, // G Report ID (3)
0x15, 0, // G Logical Minimum (0)
0x25, 1, // G Logical Maximum (1)
0x75, 1, // G Report Size (1 Bit)
0x95, 8, // G Report Count (8 Bit)
0x09, 8, // L Usage (Ausgabebit)
0x79, 8, // L String (8 = "Digitaler npn-Ausgang")
0xB1, 0x02, // M Feature (Var,NoPreferred)
// AdcReport: 18 Byte Feature
0x85, 1, // G Report ID (1)
0x09, 4, // L Usage (Flags)
0x79, 4, // L String (4 = "Neuer Wert")
0xB1, 0xA2, // M Feature (Var,NoPreferred,Volatile)
0x27, D(0xFFC0L), // G Logical Maximum (65472 = 64*0x3FF)
0x75, 16, // G Report Size (16 Bit)
0x09, 5, // L Usage (Istwert)
0x79, 5, // L String (5 = "Istwert")
0x67, D(0xF0D121), // G Unit (Spannung in Volt)
0x55, 0x0D, // G Unit Exponent (0,001 V)
0x46, W(5000), // G Physical Maximum (5,000 V)
0xB1, 0xA2, // M Feature (Var,NoPreferred,Volatile)
// DacReport: 18 Byte Feature
0x85, 2, // G Report ID (2)
0x25, 1, // G Logical Maximum (1)
0x75, 1, // G Report Size (1 Bit)
0x09, 6, // L Usage (ein/aus)
0x79, 6, // L String (6 = "aktiver D/A-Wandler")
0xB1, 0x02, // M Feature (Var,NoPreferred)
0x27, D(0xFFC0L), // G Logical Maximum (65472 = 64*0x3FF)
0x75, 16, // G Report Size (16 Bit)
0x09, 7, // L Usage (Sollwert)
0x79, 7, // L String (7 = "Sollwert")
0xB1, 0x02, // M Feature (Var,NoPreferred)
0xC0, // M End Collection
// Die Analogwerte hinten erspart es,
// die physikalischen Größen löschen zu müssen
};
// Device descriptor
static const byte PROGMEM DeviceDesc[] ={
18, // bLength
1, // descriptor type Device
W(0x0200), // USB version supported
0, // USB_CFG_DEVICE_CLASS,
0, // USB_CFG_DEVICE_SUBCLASS,
0, // protocol
EP0_SIZE,
W(0x16C0), // Voti
W(0x05DF), // V-USB: ID für generische HID-Geräte
W(0x0100),
1,
2,
3, // erforderlich für o.a. ProductID
1
};
#define CONFIG_DESC_SIZE (9+9+9+7+7)
#define HID_DESC2_OFFSET (9+9)
// Configuration descriptor
static const byte PROGMEM ConfigDesc[CONFIG_DESC_SIZE] ={
9, // bLength
2, // bDescriptorType Config
W(CONFIG_DESC_SIZE), // wTotalLength
1, // bNumInterfaces HID
1, // bConfigurationValue erste und einzige
0, // iConfiguration ohne Text
0xC0, // bmAttributes Selbstversorgt, kein Aufwecken
100/2, // MaxPower (in 2 Milliampere) 0 mA
9, // bLength
4, // bDescriptorType Interface
0, // bInterfaceNumber
0, // bAlternateSetting
2, // bNumEndpoints IN+OUT
3, // bInterfaceClass HID
0, // bInterfaceSubClass
0, // bInterfaceProtocol
0, // iInterface ohne Text
9, // bLength
0x21, // bDescriptorType
W(0x0110), // bcdHID
0, // bCountryCode
1, // bNumDescriptors
0x22, // bDescriptorType
W(sizeof HidReportDesc), // wDescriptorLength
7, // bLength
5, // bDescriptorType Endpoint
0x81, // bEndpointAddress 1 IN
0x03, // bmAttributes Interrupt
W(64), // wMaxPacketSize
10, // bInterval
7, // bLength
5, // bDescriptorType Endpoint
0x02, // bEndpointAddress 2 OUT
0x03, // bmAttributes Interrupt
W(64), // wMaxPacketSize
10, // bInterval
};
// Alle String-Deskriptoren (deutsch) hintereinander
static const wchar_t PROGMEM strLang[] =
// High-Teil = DTYPE_String, Low-Teil = (String-Länge+1)*2
L"\x0304" L"\x0407" // (0) Länge 1: deutsch
L"\x032C" L"TU Chemnitz, Enas+ZfM" // (1) Länge 21
L"\x032C" L"STS Multiplex: Gasbox" // (2) Länge 21
L"\x0330" L"heha@hrz.tu-chemnitz.de" // (3) Länge 23
L"\x0316" L"Neuer Wert" // (4) Länge 10
L"\x0310" L"Istwert" // (5) Länge 7
L"\x0328" L"Aktiver D/A-Wandler" // (6) Länge 19
L"\x0312" L"Sollwert" // (7) Länge 8
L"\x032C" L"Digitaler npn-Ausgang" // (8) Länge 21
;
/********
* Kode *
********/
// Aufruf wenn OTG-Pad Spannung führt
static void usbConnect() {
#ifdef DEBUG
PORTD|=0x01; // Ausgang 6
#endif
// PLLFRQ = 0x6A; // Lt. Datenblatt optimale PLL-Konfiguration für 5 V
// USBINT = 0;
UHWCON = 0x01; // Spannungsregler aktivieren (Bit 0 — nicht bei AT90USB162)
// USBCON = 0; // USB-Block rücksetzen, deaktiviert Pullup-Widerstand
// _delay_ms(150);
USBCON = 0xB1; // USB aktivieren, noch ohne Takt (so erforderlich)
PLLCSR = 0x12; // PLL: Takt/2, aktivieren (Bits anders als bei AT90USB162)
while (!(PLLCSR&1)); // warte bis PLL bereit
USBCON = 0x91; // USB-Takt anlegen (Bit 5 — OTG-Pad nicht bei AT90USB162)
UDCON = 0; // Pullup-Widerstand aktivieren (Bit 0)
UDINT = 0; // alle Interrupts löschen
usbCfg = 0;
// UDIEN = 0x21; // Interrupts zulassen
}
static void usbDisconnect() {
#ifdef DEBUG
PORTD&=~0x10; // Digitalausgang 8 (konfiguriert)
PORTD&=~0x02; // Digitalausgang 7 (nicht-schlafend)
PORTD&=~0x01; // Digitalausgang 6 (unter Spannung)
#endif
USBCON = 0x11; // Kein USB, nur noch OTG-Pad
PLLCSR = 0x10; // PLL abschalten
UHWCON = 0; // kein USB-Spannungsregler
UDINT = 0x01;
usbCfg = 0;
// UDIEN = 0; // keine Interrupts
}
// Misc functions to wait for ready and send/receive packets
static void usb_send_in(void) {
UEINTX = ~(1<<TXINI); // Bit 0 löschen = IN-Paket senden
}
static byte usb_wait_in_ready(void) {
byte i;
do i=UEINTX; // wait for host ready for IN packet
while (!(i & (1<<TXINI | 1<<RXOUTI)));
return i & 1<<RXOUTI;
}
/*
static void usb_wait_receive_out(void) {
while (!(UEINTX & (1<<RXOUTI))) ;
}
*/
// immer 64 Byte = volles Paket; immer aus RAM
bool usbEp1Send(const void*p) {
UENUM=1;
if (UEINTX&0x01) return false; // FIFO ist voll? Kann nicht absenden!
UEINTX=0xA0; // Alle Interrupts quittieren
const byte*a=(const byte*)p;
while (UEINTX&0x20) UEDATX=*a++; // Daten in FIFO stecken solange RWAL gesetzt ist
UEINTX=0; // Paket voll: FIFO umschalten
return true;
}
// immer 64 Byte = volles Paket
bool usbEp2Recv(void*p) {
UENUM=2;
if (!(UEINTX&0x04)) return false; // Kein RXOUTI? Keine Daten vorhanden!
UEINTX=0xA0; // Alle Interrupts quittieren
byte*a=(byte*)p;
while (UEINTX&0x20) *a++=UEDATX; // Daten aus FIFO herauslesen solange RWAL gesetzt ist
UEINTX=0; // Paket wurde abgeholt: FIFO umschalten
return true;
}
static unsigned wLength; // wLength des letzten SETUPDAT-Pakets
void usbEp0Send(const void*addr, int len) {
const byte*a=(const byte*)addr;
bool zlp=false;
if ((unsigned)len>wLength) {len=wLength; zlp=true;} // kürzen
byte n;
do{
if (usb_wait_in_ready()) return; // abort at OUT packet
n=len>EP0_SIZE?EP0_SIZE:(byte)len; // send IN packet
if (n) asm volatile(
" mov r1,%2 \n" // r1 = Zählregister
"1: sbrc r2,6 \n"
" lpm r0,Z+ \n" // vom Flash wenn Bit 6 gesetzt
" sbrs r2,6 \n"
" ld r0,Z+ \n" // vom RAM wenn Bit 6 gelöscht
" sts %1,r0 \n"
" dec r1 \n"
" brne 1b \n" // r1 ist nachher ordnungsgemäß Null
:"+z"(a):"m"(UEDATX),"r"(n)); // a wird erhöht und ist Ein- und Ausgang ("+")
usb_send_in();
}while (len-=n || zlp && n==EP0_SIZE);
}
int usbEp0Recv(void*addr, int len) {
// TODO
return len;
}
static void usbPollEP0() {
UENUM = 0;
uint8_t ueintx=UEINTX;
if (ueintx&0x08) { // Setup angekommen?
byte len;
const byte *addr;
//SETUPDAT-Paket in 8 handliche 8-Bit-Register einlesen
byte bmRequestType = UEDATX;
byte bRequest= UEDATX;
byte wValueL = UEDATX;
byte wValueH = UEDATX;
#ifdef DEBUG
UDR1=bRequest==6?wValueH+'@':bRequest+'0';
#endif
byte wIndexL = UEDATX;
byte wIndexH __attribute__((unused)) = UEDATX;
byte wLengthL= UEDATX;
byte wLengthH= UEDATX;
UEINTX=ueintx&0xF3; // Interrupt jetzt erst löschen!!
wLength=wLengthH<<8|wLengthL; // globalen Längenzähler setzen
switch (bmRequestType) {
case 0x00: switch (bRequest) { // out, device, device
case 1: // CLEAR_FEATURE
case 3: { // SET_FEATURE
if (wValueL==1) { // Feature „Remote Wakeup“
usb_send_in(); // Null-Byte-IN-Paket absenden
return;
}
}break;
case 5: { // SET_ADDRESS
usb_send_in(); // Null-Byte-IN-Paket absenden
usb_wait_in_ready(); // warte bis abgeholt
UDADDR = wValueL|0x80; // Adresse jetzt setzen
}return;
case 9: { // SET_CONFIGURATION
if (wValueL<2) {
usb_send_in(); // Null-Byte-IN-Paket absenden
usbCfg=wValueL; // setzt Alternate Settings zurück
#ifdef DEBUG
PORTD&=~0x10;
if (wValueL) PORTD|=0x10; // Digitalausgang 8
#endif
if (wValueL) {
UENUM = 1;
UECONX = 1;
UECFG0X= 0xC1; // Interrupt-IN-Endpoint
UECFG1X= 0x36; // 64 Byte doppelt gepuffert
UENUM = 2;
UECONX = 1;
UECFG0X= 0xC0; // Interrupt-OUT-Endpoint
UECFG1X= 0x36; // 64 Byte doppelt gepuffert
// UEIENX = 0x04; // Interrupt bei eingehenden OUT-Daten
}
UERST = 0x7E; // alle Endpoints außer EP0 rücksetzen
UERST = 0;
return;
}
}break;
}break;
case 0x01: switch (bRequest) { // out, device, interface
case 11: { // SET_INTERFACE
usb_send_in(); // Null-Byte-IN-Paket absenden
}return;
}break;
case 0x02: switch (bRequest) { // out, device, endpoint
case 1: // CLEAR_FEATURE
case 3: { // SET_FEATURE
if (!wValueL) { // Feature 0
byte i = wIndexL & 0x7F; // Endpoint
if (i<=2) {
usb_send_in(); // Null-Byte-IN-Paket absenden
UENUM = i;
if (bRequest&2) {
UECONX = 0x21; // Stall setzen
}else{
UECONX = 0x19; // Stall sowie Data-Toggle löschen
UERST = 1<<i; // Puffer und UEINTX löschen
UERST = 0;
}
return;
}
}
}break;
}break;
case 0x21: switch (bRequest) { // out, class, interface
case 1: { // HID_SET_REPORT
onEp0SetReport(wValueH<<8|wValueL);
}return;
}break;
case 0x80: switch (bRequest) { // in, device, device
case 0: { // GET_STATUS
usb_wait_in_ready(); // (sollte unnötig sein!!)
UEDATX=1; // Self-Powered
UEDATX=0;
usb_send_in(); // Zwei-Byte-Paket abschicken
}return; // Null-Byte-Paket ignorieren
case 6: { // GET_DESCRIPTOR
usbCfg|=0x40; // aus Flash
switch (wValueH) {
case 1: addr=DeviceDesc; break;
case 2: {
addr=ConfigDesc;
len=sizeof ConfigDesc;
}goto gd2;
case 3: {
if (wValueL>8) goto stall;
addr=(const byte*)strLang; // Zusammenhängende String-Deskriptoren
if (wValueL) do addr+=pgm_read_byte(addr); while(--wValueL);
}break;
default: goto stall;
}
gd1: len=pgm_read_byte(addr); // Länge vom Deskriptor nehmen
gd2: usbEp0Send(addr,len);
}return;
case 8: { // GET_CONFIGURATION
usb_wait_in_ready();
UEDATX = usbCfg&1;
usb_send_in();
}return;
}break;
case 0x81: switch (bRequest) { // in, device, iface
case 6: { // GET_DESCRIPTOR
usbCfg|=0x40;
switch (wValueH) {
case 0x21: addr=ConfigDesc+HID_DESC2_OFFSET; goto gd1;
case 0x22: addr=HidReportDesc; len=sizeof HidReportDesc; goto gd2;
}
}break;
case 10: { // GET_INTERFACE
if (wIndexL<2) {
usb_wait_in_ready();
UEDATX=0;
usb_send_in();
return;
}
}break;
}break;
case 0x82: switch (bRequest) { // in, device, endpoint
case 0: { // GET_STATUS
usb_wait_in_ready();
byte i=0;
UENUM = wIndexL;
if (UECONX & 1<<STALLRQ) i=1;
UENUM =0;
UEDATX=i;
UEDATX=0;
usb_send_in();
}return;
}break;
case 0xA1: switch (bRequest) { // in, class, interface
case 1: { // HID_GET_REPORT
usbCfg&=~0x40; // das Liefern aus RAM vorbereiten
onEp0GetReport(wValueH<<8|wValueL);
return;
}break;
}break;
}
stall:
UECONX = 0x21; // stall
}
}
void usbPoll() {
// Generellen USB-Interrupt behandeln
if (USBINT&1) { // Pegelwechsel am OTG-Pad?
USBINT=0;
if (USBSTA&1) usbConnect(); else usbDisconnect();
}
// USB-Device-Interrupts behandeln
byte udint=UDINT; // handliches Register
UDINT=0x01; // Interrupts löschen, außer SUSPI (Bit 0): Pad-Idle zulassen
if (udint&1<<EORSTI) {// Ende USB-Reset
UENUM = 0; // EP0 einrichten
UECFG0X= 0; // Control-Endpoint
UECFG1X= 0x32; // 64 Byte einfach gepuffert
UECONX = 1;
UEINTX = 4; // Kill Bank IN??
// UEIENX = 0x08; // Interrupt bei Setup-Paketempfang (zum Aufwecken der CPU)
usbCfg = 0; // unkonfiguriert
#ifdef DEBUG
PORTD&=~0x10; // Digitalausgang 8
#endif
}
if (udint&0x01) { // Suspend (Hardware löscht WAKEUPI)
#ifdef DEBUG
PORTD&=~0x02; // Digitalausgang 7
#endif
if (!(USBCON&0x20)) {// USB-Takt läuft noch (FRZCLK gelöscht)?
USBCON=0xB0; // USB-Takt anhalten (FRZCLK setzen)
PLLCSR=0x10; // PLL anhalten (Bit 1)
// onSuspend();
// UDIEN =0x30; // Wakeup, nicht Suspend
}
}
if (udint&0x10) { // Wakeup (Hardware löscht SUSPI) — kommt ständig
#ifdef DEBUG
PORTD|=0x02; // Digitalausgang 7
#endif
if (USBCON&0x20) { // USB-Takt angehalten (FRZCLK gesetzt)?
PLLCSR=0x12; // PLL starten
// onResume();
while (!(PLLCSR&1));// warte bis PLL eingerastet
USBCON=0x90; // USB-Takt anlegen
// UDIEN =0x21; // Suspend, nicht Wakeup
}
}
usbPollEP0();
}
#ifndef MYSTARTUP // RETIs stehen sonst direkt in der Interruptvektortabelle
EMPTY_INTERRUPT(USB_GEN_vect);
EMPTY_INTERRUPT(USB_COM_vect);
// Das funktioniert nur deshalb,
// weil nach RETI mindestens ein Befehl des Hauptprogramms abgearbeitet wird,
// bevor die leere ISR wieder aufgerufen wird.
// Daher werden die Interrupts nach sleep() umgehend gesperrt.
// Hier rächt es sich mal wieder,
// dass die AVR-Architektur kein Wakeup ohne Interrupts kennt.
#endif
Detected encoding: UTF-8 | 0
|